#include <arpa/inet.h>
#include <netdb.h>

#include "udp.h"

/**
 * Cria o socket udp e efetua o BIND na porta passada
 */
void init_udp(u_short my_port) {
    //int socket(int domain, int type, int protocol);
    // domain: PF_INET --> ipv4
    // type: SOCK_DGRAM --> udp
    // protocol: 0
    udpsockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if(udpsockfd <= 0) {
        perror("Could not open udp socket");
        exit(0);
    }

    localip = 0;
    localport = my_port;

    struct sockaddr *my_addr = get_addr(INADDR_ANY, my_port);
    int r = bind(udpsockfd, my_addr, sizeof (struct sockaddr) );
    if(r < 0) {
        perror("Could not bind udp socket");
        exit(0);
    }
    
    /* set SO_REUSEADDR on a socket to true (1): */
    int optval = 1;
    int status = setsockopt(udpsockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
    if(status == -1)
    {
        perror("Erro ao atribuir SO_REUSEADDR: ");
    }
}

void destroy_udp() {
    int r = close(udpsockfd);
    if(r < 0) {
        perror("Error closing UDP Socket");
    }
}

// TODO: testar essa funcao
ipaddr get_local_ip() {
    /*
    if(!localip)
    {
        int status;
        struct addrinfo hints;
        struct addrinfo *servinfo;
        // will point to the results
        memset(&hints, 0, sizeof hints); // make sure the struct is empty
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
        
        char ipstr[INET_ADDRSTRLEN];
        struct sockaddr_in* addr = (struct sockaddr_in*)servinfo->ai_addr;
        inet_ntop(addr->sin_family, &(addr->sin_addr), ipstr, sizeof(ipstr));
        printf("ip addres: %s\n", ipstr);

        01111111000000000000000000000001
        //hints.ai_flags = AI_PASSIVE;
        // fill in my IP for me
        if ((status = getaddrinfo(INADDR_ANY, NULL, &hints, &servinfo)) != 0) {
            fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
            exit(1);
        }

        // servinfo now points to a linked list of 1 or more struct addrinfos
        // ... do everything until you don't need servinfo anymore ....
        freeaddrinfo(servinfo); // free the linked-list
        localip = addr->sin_addr.s_addr;
    }
    */
    return (ipaddr)2130706433;
}

/**
 * Monta uma estrutura sockaddr a partir do endereco ip
 * e da porta passados
 * @param my_ip endereco ip
 * @param my_port porta
 * @return estrutura sockaddr
 */
struct sockaddr* get_addr(ipaddr my_ip, u_short my_port) {
    struct sockaddr_in *my_addr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
    my_addr->sin_family = AF_INET;
    my_addr->sin_port = htons(my_port);
    // short, network byte order
    my_addr->sin_addr.s_addr = my_ip;
    memset(my_addr->sin_zero, '\0', sizeof my_addr->sin_zero);

    return (struct sockaddr*)my_addr;
}
struct sockaddr* get_addr_ipstr(const char* my_ipstr, u_short my_port) {
    return get_addr(inet_addr(my_ipstr), my_port);
}

/**
 * Envia uma mensagem via UDP
 * @param ip_dst ip de destino
 * @param port_dst porta de destino
 * @param msg mensagem a ser enviada
 * @param length tamanho da mensagem
 * @return 0 em caso de sucesso, -1 em caso de erro
 */
int udpsend(ipaddr ip_dst, u_short port_dst, void* msg, int length) {
    if(udpsockfd <= 0)
        return SYSERR;

    struct sockaddr *my_addr = get_addr(ip_dst, port_dst);

    printf("sending a packet:\tip:%d\tporta:%d\n",
        ((struct sockaddr_in*)my_addr)->sin_addr.s_addr, ((struct sockaddr_in*)my_addr)->sin_port);

    return sendto(udpsockfd, msg, length, 0, my_addr, sizeof(struct sockaddr));
}
int udpsend_ipstr(const char* ipstr_dst, u_short port_dst, void* msg, int length) {
    return udpsend(inet_addr(ipstr_dst), port_dst, msg, length);
}
/**
 * Recebe uma mensagem via UDP (Recvfrom)
 * @param ip_src ip de origem
 * @param port_src porta de origem
 * @param buff buffer da mensagem
 * @param length tamanho do buffer
 * @return tamanho da mensagem
 */
int udprecv(ipaddr ip_src, u_short port_src, void* msg, int length) {
    if(udpsockfd <= 0)
        return SYSERR;
    
    struct sockaddr *my_addr = get_addr(ip_src, port_src);
    socklen_t* addrlen = (socklen_t*)malloc(sizeof(socklen_t));
    return recvfrom(udpsockfd, msg, length, 0, my_addr, addrlen);
}
int udprecv_ipstr(const char* ipstr_src, u_short port_src, void* msg, int length) {
    return udprecv(inet_addr(ipstr_src), port_src, msg, length);
}